Reusability is a key aspect of software engineering as it allows for applying a once developed solution at several places. Regarding BDI agents reusability can be achieved by two different approaches. First, in BDI V3 it is possible to exploit the existing Java inheritance mechanism and design a base agent class that contains common functionality and is extended by different application agent classes. Of course, this mechanism suffers from the fact that in Java no multi-inheritance is possible. Hence, in case you want to reuse different functionalities these can be encapsulated in so called BDI capabilities. A BDI capability represents a module that may contain belief, goals and plans like a normal agent. Capabilities realize a hierarchical (de)composition concept meaning that it is possible to include any number of subcapabilities that may again represent composite entities. 

A module has to provide an explicit boundary which allows for connecting it with an agent or with another module. In contrast to BDI V2, in which it had to be explicitly declared which beliefs, goals and plans are exported and thus visible to the outside of a module, in BDI V3 these specifications have been pushed to the Java level. This means that the visibility modifiers you use in Java determines also if beliefs, goals and plans are visible.

There is basically one additional feature that goes beyond these rules. In order to allow the specification of abstract beliefs, which should be available in the module but are made concrete and are assigned at the level of the outer, i.e. including module, unimplemented beliefs can be specified. Such unimplemented beliefs are represented as native getter/setter pairs without method body. In the outer capability an explicit belief mapping has to be stated which describes the connection of a local and the abstract belief of the submodule. 

In Jadex V3 a capability is typically represented as a Class:
As a capability should enable reuse it is the normal case to use a separate class file for the module. The module is declared and instantiated as normal field in the agent with corresponding meta information in terms of an annotation. 

E1 - Creating a Capability

In this first exercise we just create a capability that encapsulates the translation agent behaviour. The agent itself is reduced to use the capability and dispatch a translation goal from the capability.

  • Create a new class file called TranslationCapability and add the @Capability annotation above the class definition.

@Capability
public class TranslationCapability
{
  ...
}

  • Add a belief named wordtable of type Map<String, String>  and create an instance of it.
  • Add an empty constructor to TranslationCapability, which adds some word pairs to the word table.

For the actual goal we will use an inner class called Translate:

  • Create a goal as inner class named Translate. Add two fields of type String named eword ang gword to the inner class and annotate them with @GoalParameter and @GoalResult respectively. Also add a constructor which takes the eword as parameter and assigns it to the goal parameter.
  • Add a method plan that reacts to the translate goal. It should take the eword as parameter and return the translated word using the normal lookup in the word table. (Annotate with @Plan(trigger=@Trigger(goals=Translate.class)) )

Last we will need to create the actual agent:

  • Create an empty agent file called TranslationBDI with the corresponding annotation.
  • Add a field of type IBDIAgentFeature called bdi and add the @AgentFeature annotation.
  • Add a field called capability of type TranslationCapability , annotate it with @Capability and assign an instance of it.

@Capability
protected TranslationCapability capa = new TranslationCapability();

  • Create an agent body which creates and dispatches a translation goal from the included capability. Wait for the goal to be finished and print out the result of the translation.

String eword = "dog";
String gword = (String) bdi.dispatchTopLevelGoal(capa.new Translate(eword)).get();
System.out.printf("Translating %s to %s", eword, gword);

Starting and testing the agent

After starting the agent you should see again the print out of the translated word.

E2 - Using an Abstract Belief

In this exercise we will show how an abstract belief can be used. The application idea here is that the agent itself manages the word table (instead of the capability) and provide another plan to search for synonyms in that table. (In this simple case an alternative solution would have been making the word table accessible via public getter/setter methods. But the design here suggests that if more functionalities share a data structure that none of them really owns exclusively that it is better to move it to the agent level).

  • Copy both files from the last lecture and perform the following modifications.
  • Delete the wordtable belief and the constructor from the capability definitions.
  • Add a native getter/setter pair, i.e. an abstract belief named get- and setWordtable.

@Belief
public native Map<String, String> getWordtable();

@Belief
public native void setWordtable(Map<String, String> wordtable);

  • In the translate plan body replace the direct field access with a getWordtable call.
  • In the agent file change the capability definition to include the necessary belief mapping.

@Capability(beliefmapping=@Mapping(value="wordtable"))
protected TranslationCapability capa = new TranslationCapability();

  • Add/move the definition of the word table belief to the agent.
  • Add an agent init method and create word pairs as well as synonyms. For example the following:

@AgentCreated
public void init()
{
  wordtable.put("coffee", "Kaffee");
  wordtable.put("milk", "Milch");
  wordtable.put("cow", "Kuh");
  wordtable.put("cat", "Katze");
  wordtable.put("dog", "Hund");
  wordtable.put("puppy", "Hund");
  wordtable.put("hound", "Hund");
  wordtable.put("jack", "Katze");
  wordtable.put("crummie", "Kuh");
}

  • Add a plan called findSynonyms which iterates through the word table and collects synonyms.

@Plan
protected List<String> findSynonyms(ChangeEvent ev)
{
  String eword = (String)((Object[])ev.getValue())[0];
  List<String> ret = new ArrayList<String>();
  String gword = wordtable.get(eword);
  for(String key: wordtable.keySet())
  {
    if(wordtable.get(key).equals(gword))
    {
      ret.add(key);
    }
  }
  return ret;
}